<?php
namespace modules\iChat\objects;

use modules\iChat\libs\FS;

class WebSocket
{
	const CONNECT_NUM = 3;//连接数
    const FILE_SOCKET = '/modules/iChat/data/socket-debug.log';

	public static $bindNum  = 0;//成功连接数目
	public static $connect = []; //监听socket  和 连接socket 合集
    private $master; // 监听socket
    private $handshake = [];//
	
	public function __construct($addr, $port)
	{
        $this->master = socket_create(AF_INET, SOCK_STREAM, 0);
        socket_bind($this->master, $addr, $port) or die('Could not bind to address'); 
        socket_listen($this->master, self::CONNECT_NUM); 
        //socket_set_nonblock($this->master);
        $this->assembleSock($this->master, '', 'master');
        while (true) {
            $this->server();
        }
        socket_close($this->master); 
	}
    /**
     * 组装sock合集
     * @param  resource $sock 
     * @param  string $uid  用户ID
     * @return array       
     */
    private function assembleSock($sock, $uid = '', $type = 'connect')
    {
        self::$connect[(int) $sock] = [
            'uid' => $uid, 
            'resource' => $sock,
            'type'   => $type,
        ];
    }
    /**
     * 服务
     * @return void
     */
	public function server()
	{
        $socks = array_column(self::$connect, 'resource');
        $write = NULL;
        $except = NULL;
        $readNum = socket_select($socks, $write, $except, NULL);//NULL必须有变量
        if ($readNum ===  false) {
            $this->debug([
                'socket read num false',
                 $err_code = socket_last_error(),
                socket_strerror($err_code)
            ]);
            return;
        }
        foreach ($socks  as $sock) {
            if ($sock == $this->master) {
                $this->masterListen($sock);
            } else {
                $this->newsListen($sock);
            }
        }
	}
    /**
     * 监听socket
     * @param  resource $sock
     * @return void      
     */
	public function masterListen($sock){
		if ($newsock = socket_accept($sock)) { 
	        if (is_resource($newsock)) {
	        	$buffer = socket_read($newsock, 1024);
	        	if (!isset($this->handshake[(int) $newsock])) {
		        	if ($this->handShake($newsock, $buffer)) {//webSocket握手
		        		$this->handshake[(int) $newsock] = true;
		        		$this->assembleSock($newsock);
		        	} else {
		        		$this->debug([
		        			'connect new sock fail'
		        		]);
		        	}
	        	}
	            ++self::$bindNum;//连接socket数
                echo "NOW, SERVER HAS CONNECT NUM::" . self::$bindNum ."\n";
	        }
		}
	}
    /**
     * 连接socket
     * @param  resouce $sock 
     * @return void      
     */
	public function newsListen($sock)
	{
		$stream = $this->read($sock);
		if (is_array($stream)) {
            //static $num = 0;
            //doservice
            //type+ID 点点|群组  注意踢出组员，socket过滤
                    //点对点 type=1  id 对方号码   msg
                    //点对组 type=2  id 组号码   msg
            $stream  = self::checkStream($stream);
            $uid     = (string)$stream['uid'];
            $tid     = (string)$stream['tid'];
            $type    = (string)$stream['type'];
            $msg     = $stream['msg'];
            if (!array_key_exists($uid, Session::$uid2Sock)) {
                if ($uid) {
                    Session::$uid2Sock[$uid] = (int)$sock;
                    Session::$sock2Uid[(int) $sock] = $uid;
                }
            }
            var_dump(Session::$uid2Sock, Session::$sock2Uid);
            $session = Session::getInstance($uid, $tid, $type, $msg);
            $session->send();
		}
	}
    /**
     * 检测函数并初始化
     * @param  Array  $stream 
     * @return          
     */
    public static function checkStream(Array $stream)
    {
        $baseParams = ['uid','tid','type','msg'];
        if ($keys = array_diff($baseParams, array_keys($stream))) {
            foreach ($keys as $one) {
                $stream[$one] = '';
            }
        }
        return $stream;
    }
    /**
     * 读取socket
     * @param   $sock 
     * @return  array|bool     
     */
	public static function read($sock)
    {
		$stream = socket_read($sock, 1024);
		if (strlen($stream) < 9) {//客户端socket异常
			unset(self::$connect[(int) $sock]);
            unset(Session::$uid2Sock[Session::$sock2Uid[(int) $sock]]);
            unset(Session::$sock2Uid[(int) $sock]);
            --self::$bindNum;
			return false;
		}
		return self::parse(trim($stream));
	}
	/**
	 * 写入socket
	 * @param  resource $sock 
	 * @param  Array $msg  
	 * @return void
	 */
	public static function write($sock, Array $msg) 
	{
		$msg = self::build($msg);
		socket_write($sock, $msg, strlen($msg)); 
	}
    /**
     * webSocket握手协议
     * @param   $socket 
     * @param   $buffer 
     * @return  void     
     */
    public function handShake($socket, $buffer)
    {
        // 获取到客户端的升级密匙
        $line_with_key = substr($buffer, strpos($buffer, 'Sec-WebSocket-Key:') + 18);
        $key = trim(substr($line_with_key, 0, strpos($line_with_key, "\r\n")));
        // 生成升级密匙,并拼接websocket升级头
        $upgrade_key = base64_encode(sha1($key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true));// 升级key的算法
        $upgrade_message = "HTTP/1.1 101 Switching Protocols\r\n";
        $upgrade_message .= "Upgrade: websocket\r\n";
        $upgrade_message .= "Sec-WebSocket-Version: 13\r\n";
        $upgrade_message .= "Connection: Upgrade\r\n";
        $upgrade_message .= "Sec-WebSocket-Accept:" . $upgrade_key . "\r\n\r\n";
        socket_write($socket, $upgrade_message, strlen($upgrade_message));// 向socket里写入升级信息
        socket_getpeername($socket, $ip, $port);
        $this->debug([
            'hand_shake success',
            $socket,
            $ip,
            $port
        ]);
        // 向客户端发送握手成功消息,以触发客户端发送用户名动作;
        /*$msg = [
            'type' => 'handshake',
            'content' => 'done',
        ];
        self::write($socket, $msg);*/
        return true;
    }
    /**
     * 将普通信息组装成websocket数据帧
     * @param $msg
     * @return string
     */
    private static function build($msg)
    {
    	$msg = is_array($msg) ? json_encode($msg) : $msg;
        $frame = [];
        $frame[0] = '81';
        $len = strlen($msg);
        if ($len < 126) {
            $frame[1] = $len < 16 ? '0' . dechex($len) : dechex($len);
        } else if ($len < 65025) {
            $s = dechex($len);
            $frame[1] = '7e' . str_repeat('0', 4 - strlen($s)) . $s;
        } else {
            $s = dechex($len);
            $frame[1] = '7f' . str_repeat('0', 16 - strlen($s)) . $s;
        }

        $data = '';
        $l = strlen($msg);
        for ($i = 0; $i < $l; $i++) {
            $data .= dechex(ord($msg{$i}));
        }
        $frame[2] = $data;

        $data = implode('', $frame);

        return pack("H*", $data);
    }

    /**
     * 解析webSocket数据
     * @param $buffer
     * @return bool|string
     */
    private static function parse($buffer) {
        $decoded = '';
        $len = ord($buffer[1]) & 127;
        if ($len === 126) {
            $masks = substr($buffer, 4, 4);
            $data = substr($buffer, 8);
        } else if ($len === 127) {
            $masks = substr($buffer, 10, 4);
            $data = substr($buffer, 14);
        } else {
            $masks = substr($buffer, 2, 4);
            $data = substr($buffer, 6);
        }
        for ($index = 0; $index < strlen($data); $index++) {
            $decoded .= $data[$index] ^ $masks[$index % 4];
        }

        return json_decode($decoded, true);
    }

    /**
     * 记录debug信息
     * @param array $info
     */
    private function debug(array $info)
    {
        $time = date('Y-m-d H:i:s');
        array_unshift($info, $time);
        $info = array_map('json_encode', $info);
        FS::write(implode(' | ', $info), PATH_BASE . self::FILE_SOCKET);
    }
}